JavaScript 逆向工程与防护技术解析
1. 引言
日常刷隐藏价、抢预约码、用网页版小工具时,你有没有好奇——为什么复制别人抓的API请求包没用?为什么打开浏览器Sources面板看不到完整的、能读的代码?这里面藏着Web开发者的「双重数据缓冲」:前端交互逻辑的“黑箱化”,和后端API调用的“准入锁”。本文就从前端JS防护实战、简单逆向拆解思路两方面,快速讲明白这些接地气的技术套路。
2. 网站前端+后端的轻量数据防护方案
先看一套完整的「防恶意调用」组合拳:后端锁死API参数,前端把生成参数的逻辑藏起来。
2.1 URL/API 参数加密/校验
核心作用: 防止未授权的爬虫或个人直接复制API请求包调用
常见实现方式:
- 前后端共同维护算法规则/密钥
- 常用工具链:Base64/Hex(简单转义)、MD5/SHA256(不可逆防篡改)、AES/DES(可逆加密敏感内容)、RSA(交换密钥用)
- 典型产品场景:
- 抢票平台:每次请求带动态
sign(签名)、ts(时间戳)、nonce(随机数)
- 电商隐藏价:展示前才解密API返回的加密数据
2.2 JavaScript 前端代码防护技术
前端是离用户最近的地方,生成参数的逻辑、解密数据的步骤都会在这里暴露,所以需要「黑箱加固」。常用的有3种:代码压缩、代码混淆、WebAssembly。
2.2.1 代码压缩
入门级防护:主要目的不是防人,是优化加载速度+顺便模糊变量短名,完全格式化回来就能读。
原理:去除所有冗余(空格、换行、注释)、把长变量/函数名改成1-2个字符的简写
常用工具:Terser(Webpack/Vite/Rollup的默认压缩工具)
示例对比:
// 🟢 压缩前(人人都能读)
function calculateDiscountPrice(originalPrice, discountPercent, isVip) {
// VIP额外打9折
if (isVip) {
discountPercent += 10;
}
// 计算最终价格,保留两位小数
const finalPrice = originalPrice * (1 - discountPercent / 100);
return Number(finalPrice.toFixed(2));
}
// 🔴 压缩后(稍微有点晃眼,但格式化后秒懂)
function calculateDiscountPrice(o,d,i){return i&&(d+=10),Number((o*(1-d/100)).toFixed(2))}
2.2.2 代码混淆
进阶级防护:专门用来「恶心逆向者」,即使格式化了也像看天书,还原难度取决于混淆级别。
核心混淆方式对比:
最常用的免费混淆工具:javascript-obfuscator(有在线版,也有CLI/Node.js版本)
2.2.3 WebAssembly
硬核级防护:直接把前端的核心逻辑(比如生成复杂sign的算法、解密敏感数据的步骤)从JS搬到C/C++/Rust里,编译成二进制文件.wasm,JS只负责调用。
为什么难逆向?
- 不是可读的JS代码,是机器能直接运行的二进制
- 逆向需要懂汇编语言门槛高
- 速度比JS快、体积比JS小,开发者也愿意用
3. JavaScript混淆实战(用 javascript-obfuscator)
这里用Node.js版本演示,适合批量混淆项目代码。
3.1 基础混淆配置
先把基础的「代码压缩+变量乱改+字符串藏起来」加上:
// 引入依赖,先 npm install javascript-obfuscator
const JavaScriptObfuscator = require('javascript-obfuscator');
const fs = require('fs');
// 读取要混淆的原始代码
const originalCode = fs.readFileSync('original.js', 'utf8');
// 基础但有效的混淆选项
const obfuscationOptions = {
compact: true, // 压缩成一行(可选false)
controlFlowFlattening: true, // 必加!核心的控制流打乱
stringArray: true, // 必加!字符串阵列化
stringArrayEncoding: ['base64'], // 字符串再加一层Base64
selfDefending: true, // 必加!自我保护,格式化后代码也不能正常运行
};
// 生成混淆后的代码
const obfuscatedResult = JavaScriptObfuscator.obfuscate(originalCode, obfuscationOptions);
const obfuscatedCode = obfuscatedResult.getObfuscatedCode();
// 写入文件
fs.writeFileSync('obfuscated.js', obfuscatedCode);
console.log('混淆完成!已生成 obfuscated.js');
3.2 进阶混淆配置(可选)
如果预算充足/安全要求高,可以再加这些选项(注意:混淆级别越高,代码运行速度越慢):
const advancedOptions = {
// 变量名改成更复杂的hex16字符串,甚至混淆全局变量
identifierNamesGenerator: 'hexadecimal',
renameGlobals: true,
// 无限循环的debugger,打开开发者工具就不停弹
debugProtection: true,
debugProtectionInterval: true,
// 绑定指定域名,防止复制到其他地方用
domainLock: ['your-website.com', 'www.your-website.com'],
// 字符串再加一层Unicode转码,看起来更乱
unicodeEscapeSequence: true,
};
4. WebAssembly 简单示例(用Emscripten)
假设我们要把「计算动态sign的核心函数」搬到WASM里:
4.1 写C代码
// 保存为 add_sign.c(模拟简单的加法+拼接生成sign)
#include <string.h>
#include <stdio.h>
// 拼接数字和固定字符串生成sign(真实场景可以更复杂)
void generate_sign(int ts, int nonce, char* output, int output_len) {
snprintf(output, output_len, "sign_%d_%d", ts, nonce);
}
4.2 用Emscripten编译成WASM
先安装Emscripten(官网有一键安装脚本),然后运行:
emcc add_sign.c -o add_sign.js -s WASM=1 -s EXPORTED_FUNCTIONS="['_generate_sign']" -s EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap']"
会生成两个文件:add_sign.js(JS调用WASM的桥梁)、add_sign.wasm(核心二进制逻辑)
4.3 在网页里调用
<!DOCTYPE html>
<html>
<head>
<title>WASM生成Sign示例</title>
</head>
<body>
<script src="add_sign.js"></script>
<script>
// 等WASM加载完成
Module.onRuntimeInitialized = function() {
// 把C函数转成JS能直接调用的函数
const generateSign = Module.cwrap('generate_sign', null, ['number', 'number', 'string', 'number']);
// 生成动态参数
const ts = Date.now();
const nonce = Math.floor(Math.random() * 1000000);
// 分配内存给输出的sign
const outputBuffer = Module._malloc(100);
generateSign(ts, nonce, outputBuffer, 100);
// 读取内存里的sign
const sign = Module.UTF8ToString(outputBuffer);
console.log('生成的动态参数:', { ts, nonce, sign });
// 释放内存
Module._free(outputBuffer);
};
</script>
</body>
</html>
5. 逆向分析的简单思路(供安全自查用)
如果网站已经上线,可以用这些简单方法自查防护有没有效果:
5.1 浏览器开发者工具自查
- Network面板:全局搜索「api」「get」「sign」「token」,看API请求有没有加密参数,有没有明文返回敏感数据
- Sources面板:随便找个JS文件,点「格式化代码」(左下角的
{} 按钮),看能不能找到关键逻辑
- Console面板:按
F12 打开,看会不会不停弹 debugger,或者直接报错
5.2 Hook简单API自查
如果格式化后还是找不到生成参数的逻辑,可以用Hook关键函数的方法捕获参数:
// Hook Fetch请求(复制到Console面板运行)
const originalFetch = window.fetch;
window.fetch = function(url, options) {
console.log('🔍 拦截到Fetch请求:');
console.log('URL:', url);
console.log('Options:', options);
return originalFetch.apply(this, arguments);
};
// Hook XMLHttpRequest请求(复制到Console面板运行)
const originalXHR = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
console.log('🔍 拦截到XHR请求:');
console.log('Method:', method);
console.log('URL:', url);
this._method = method;
this._url = url;
return originalXHR.apply(this, arguments);
};
6. 实用防护建议(性能与安全平衡)
不要追求「100%不可逆向」(没有绝对安全的代码),重点是提高逆向的时间成本,让恶意调用者放弃:
- 分层防护,核心逻辑特殊处理:
- 普通的页面交互逻辑:只用代码压缩
- 生成简单参数的逻辑:用基础混淆
- 生成核心sign、解密敏感数据的逻辑:用WebAssembly
- 定期更新混淆策略/密钥:比如每周换一次
javascript-obfuscator 的种子密钥
- 控制混淆级别:进阶混淆会让代码运行速度变慢10%-30%,核心交互(比如按钮点击、下拉加载)不要加太高级的混淆
- 不要只依赖前端防护:后端一定要加鉴权(token、IP限流、sign校验),前端只是第一道缓冲
7. 总结
现代Web应用的防护是「前端+后端」的组合拳:
- 后端:锁死API参数(sign、ts、nonce)、IP限流、鉴权
- 前端:把普通逻辑压缩、把重要逻辑混淆、把核心逻辑搬到WebAssembly
了解这些技术,既能帮开发者构建更安全的网站,也能帮安全研究人员快速定位漏洞。
示例代码仓库:
https://github.com/Python3WebSpider/JavaScriptObfuscate